#include <QFile>
#include <QXmlInputSource>
#include <QXmlSimpleReader>
#include <util/message.hpp>
#include "vvm.hpp"

namespace {
  util::message_stream<char> vverr ("\nVVM ERROR: ");
  util::message_stream<char> vvwarn("\nVVM WARNING: ");
}

/** @brief Constructor */
vv::vvm_handler::vvm_handler() :
  f(0),
  s(0)
{}

/** @brief Destructor */
vv::vvm_handler::~vvm_handler() {}

/** @brief Specify the vset to populate and if the global variables
           shgould be read
    @param f The vertex factory to use
    @param s The vset to populate
    @param read_global

    This function should be called prior to passing the handler to the
    function @c read_vvm.

    If s is null when read_vvm is called, the @c v and @c e tags will
    be ignored.  If read_global is false, the @c g tags are ignored.

    If f is null, an error is reported and initialisation is stopped.
*/
void vv::vvm_handler::init(vv::vertex_factory* f, vv::vset* s, bool read_global) {
  if (!f) {
    vverr << "vvm_handler::init - The pointer to the vertex factory cannot be null." << std::endl;
    return;
  }

  this->f = f;
  this->s = s;
  g = read_global;
}

/** @brief Report an error in the vvm file
    @param e The exception with the error information
*/
bool vv::vvm_handler::fatalError(const QXmlParseException & e) {
  vverr << "On line" << e.lineNumber()
	<< ", column" << e.columnNumber() << ":"
	<< e.message().toStdString()
        << std::endl;
  return false;
}

/** @brief Process a tag from the vvm file. */
bool vv::vvm_handler::startElement(const QString&, const QString&, const QString& qName, const QXmlAttributes& atts) {
  if (qName == "v" && s) {
    vptr v = f->create_vertex();
    for (int i = 0; i < atts.length(); ++i) {
      std::istringstream value(atts.value(i).toStdString());
      if (atts.qName(i) == "nb") {
	vnb n;
	n.v = v;
	while (!value.eof()) {
	  unsigned int label;
	  value >> std::ws >> label >> std::ws;
	  n.nb.push_back(label);
	}
      }
      else
	vprop(v, atts.qName(i).toStdString(), value);
    }
  }
  else if (qName == "e" && s) {
    e edge;
    for (int i = 0; i < atts.length(); ++i) {
      if (atts.qName(i) == "first")
	edge.first     = atts.value(i).toInt();
      else if (atts.qName(i) == "second")
	edge.second    = atts.value(i).toInt();
      else if (atts.qName(i) == "symmetric")
	edge.symmetric = atts.value(i).toInt();
      else
	edge.properties.push_back(std::make_pair(atts.qName(i).toStdString(), atts.value(i).toStdString()));
    }
    edges.push_back(edge);
  }
  else if (qName == "g" && g) {
    for (int i = 0; i < atts.length(); ++i)
      gprop(atts.qName(i).toStdString(), atts.value(i).toStdString());
  }

  return true;
}

/** @brief End of the vvm processing

    This function is responsible for taking the data collected from
    the vvm file and sorting it out if necessary (i.e. assignment of
    the vertex neighbourhoods) and cleaning any temporary resources
    used while reading the file.

    If there is an invalid index (the index is larger than the number
    of vertices in the file) in a neighbourhood, a warning is issued
    and that index is ignored.

    If there is an invalid index in an edge, that edge will be
    ignored.
*/
bool vv::vvm_handler::endDocument() {
  if (s) {
    for (unsigned int i = 0; i < vertices.size(); ++i) {
      vv::nb nb;
      for (unsigned int l = 0; l < vertices[i].nb.size(); ++l) {
	if (l >= vertices.size())
	  vvwarn << "The index " << l << " was found in neighbourhood of vertex " << i << " is invalid; the invalid index will be ignored." << std::endl;
	else
	  nb.push_back(vertices[l].v);
      }
      vertices[i].v->assign_nb(nb);
      s->add(vertices[i].v);
    }

    for (unsigned int i = 0; i < edges.size(); ++i) {
      if (edges[i].first >= vertices.size())
	vvwarn << "The index " << edges[i].first << " found in an edge going to " << edges[i].second << " is invalid; the edge will be ignored." << std::endl;
      else if (edges[i].second >= vertices.size())
	vvwarn << "The index " << edges[i].second << " found in an edge going from " << edges[i].first << " is invalid; the edge will be ignored." << std::endl;
      else {
	for (unsigned int j = 0; j < edges[i].properties.size(); ++j)
	  eprop(
	    vertices[edges[i].first].v,
	    vertices[edges[i].second].v,
	    edges[i].symmetric,
	    edges[i].properties[j].first,
	    edges[i].properties[j].second
	  );
      }
    }
  }

  vertices.clear();
  edges.clear();
  s = 0;

  return true;
}

/** @brief Parse a vvm file with a supplied xml handle
    @param h The handler object to use
    @param filename The name of the vv file

    The reader object @a r should be a class derived from @c
    vv::vv_reader such that it has methods to read the vertex and edge
    properties specific to a particular vertex-derived type.  A
    wrapper for this function is generated when the vertex definition
    preprocessor is used such that the generated reader is used.

    If the vvm cannot be opened or parsed, an error message is issued
    and no changes are made to the data.
*/
void vv::read_vvm(util::undeletable_ptr<vv::vvm_handler> h, std::string filename) {
  QFile file(filename.c_str());

  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    vverr << "The file " << filename << " could not be opened for reading." << std::endl;
    return;
  }

  QXmlSimpleReader reader;
  QXmlInputSource *source = new QXmlInputSource(&file);

  reader.setContentHandler(h.raw());
  reader.setErrorHandler(h.raw());
  if (!reader.parse(source)) {
    vverr << "There was an error parsing the vvm file " << filename << "." << std::endl;
    return;
  }
}
